#include "state.h"


State::State(int n)
    : common_dimension(n)
{
    rank = shmem_my_pe();
    size = shmem_n_pes();
    try  {
        if (power_2((long long)size) < 0)
            throw "Error: Use 2^k process";
    } catch (const char* str) {
        if (rank == 0)
            cout << str << endl;
        exit(2);
    }
    dimension = common_dimension - power_2((long long)size);
    common_length = 1L << common_dimension;
    length = 1L  << dimension;
    define_size();
    begin = rank * length;
    end = begin + length - 1;
    vec = new complexd [length];

    shmem_register_handler(Recieve,1);
    temppp = new complexd [size_block];
}


State::State(const State &obj)
{
    dimension = obj.dimension;
    common_dimension = obj.common_dimension;
    length = obj.length;
    common_length = obj.common_length;
    size_block = obj.size_block;
    begin = obj.begin;
    end = obj.end;
    rank = obj.rank;
    size = obj.size;
    vec = new complexd [length];
    for (long long i = 0; i < length; i++)
        vec[i] = obj.vec[i];
}


State::~State()
{
    delete [] vec;
}


void State::doConversion(Conversion &conversion, int k)
{
    complexd *vec_tmp = 0, *vec_new = 0;
    long long l, index, real_index, block;
    int n = common_dimension, k_bit;

    complexd t[] = {conversion[0][0], conversion[0][1],
                    conversion[1][0], conversion[1][1]};

// define rank of communicate processor
    if ((l = set_k_bit_i_0(begin,k,n)) == begin)
         l = set_k_bit_i_1(begin,k,n);
    int proc = what_is_rank(l);

    if (proc != rank)
        vec_tmp = new complexd [size_block];
    else {
        vec_tmp = vec;
        vec_new = new complexd [length];
    }

    for (l = 0; l < length / size_block; l++) {
        block = size_block * l;

// send and recieve to buffer vec_tmp
        if (proc != rank) {
            Sendrecv(&vec[block], proc);
            vec_tmp = temppp;
            shmem_barrier_all();
        }
            /*
            MPI_Sendrecv(&vec[block], size_block, MPI_DOUBLE_COMPLEX, proc, l,
                             vec_tmp, size_block, MPI_DOUBLE_COMPLEX, proc, l,
                                                     MPI_COMM_WORLD, &status);
            */

        if (rank < proc) {
            for (long long i = 0; i < size_block; i++) {
                index = block + i;
                real_index = begin + index;
                k_bit = k_bit_of_i(real_index, k, n);
                vec[index] = t[k_bit * 2 + 0] * vec[index]
                           + t[k_bit * 2 + 1] * vec_tmp[i];
            }
        } else if (rank > proc) {
            for (long long i = 0; i < size_block; i++) {
                index = block + i;
                real_index = begin + index;
                k_bit = k_bit_of_i(real_index, k, n);
                vec[index] = t[k_bit * 2 + 0] * vec_tmp[i]
                           + t[k_bit * 2 + 1] * vec[index];
            }
        } else {
            for (long long i = 0; i < size_block; i++) {
                index = block + i;
                real_index = begin + index;
                k_bit = k_bit_of_i(real_index, k, n);
                vec_new[index] = t[k_bit * 2 + 0] * vec[set_k_bit_i_0(real_index,k,n) - begin]
                               + t[k_bit * 2 + 1] * vec[set_k_bit_i_1(real_index,k,n) - begin];
            }
        }
    }
    shmem_barrier_all();
    if (vec_tmp != vec)
        delete [] vec_tmp;
    if (vec_new != 0) {
        delete [] vec;
        vec = vec_new;
    }
}


void State::Sendrecv(complexd *data, int proc)
{
    elem_ind tmp_send[size_block];
    for (long long i = 0; i < size_block; i++) {
        tmp_send[i].data = data[i];
        tmp_send[i].ind = i;
        shmem_send(&tmp_send, 1, sizeof(elem_ind), proc);
    }
    shmem_barrier_all();
    return;
}

void Recieve(int from, void *data, int sz)
{
    elem_ind *tmp = (elem_ind*)data;
    temppp[tmp->ind] = tmp->data;
}

void State::nConversion(Conversion &con)
{
    for (int i = 1; i <= common_dimension; i++) {
        con.doNoize();
        doConversion(con, i);
    }
}

void State::n0Conversion(Conversion &con)
{
    for (int i = 1; i <= common_dimension; i++) {
        doConversion(con, i);
    }
}


complexd State::vector(long long index, complexd* tmp, int k)
{
    complexd a(0,0);
    if (index >= begin && index  <= end)
        return vec[index - begin];
    else  {
        if (set_k_bit_i_0(index, k, common_dimension) == index)
           index = set_k_bit_i_1(index, k, common_dimension);
        else
            index = set_k_bit_i_0(index, k, common_dimension);
        return tmp[(index - begin) % size_block];
    }
}

void State::define_size()
{
    size_block = (length > SIZE_BLOCK ? SIZE_BLOCK : length);
}

int State::what_is_rank(long long index)
{
    return index / length;
}

int State::what_is_index(long long index)
{
    return index % length;
}

int State::power_2(long long base)
{
    int power, i;
    for (i = 1, power = 0; i < base; power++)
        i = i << 1;
    if (i ==  base)
        return power;
    else
        return -1;
}


void State::norma()
{
    int root = 0;
    double sum = 0;
    for (long long i = 0; i < length; i++)
        sum += abs(vec[i]) * abs(vec[i]);
    shmem_double_allsum(&sum);
    shmem_barrier_all();
    for (long long i = 0; i < length; i++)
        vec[i] = vec[i] / (double)sum;
}


void State::randomFill()
{
    srand(time(NULL));
    for (long long i = 0; i < length; i++)
        vec[i] = myrandom();
}

void State::intentionalFill(long long com_length, complexd* new_vec)
{
    common_length = com_length;
    try  {
        if ((common_dimension = power_2(common_length)) < 0)
            throw "Error: vector must be 2^k";
    } catch (const char* str) {
        if (rank == 0)
            cout << str << endl;
        exit(3);
    }
    delete [] vec;
    dimension = common_dimension - power_2((long long)size);
    length = 1L << dimension;
    define_size();
    begin = rank * length;
    end = begin + length - 1;
    vec = new complexd [length];
    for (long long i = 0; i < length; i++)
        vec[i] = new_vec[i + begin];
}

State State::operator=(State state)
{
    common_dimension = state.common_dimension;
    dimension = state.dimension;
    common_length = state.common_length;
    length = state.length;
    begin = state.begin;
    end = state.end;
    size_block = state.size_block;
    rank = state.rank;
    size = state.size;
    vec = new complexd [length];
    for (int i = 0; i < length; i++)
        vec[i] = state.vec[i];
    return *this;
}

bool State::operator==(State state)
{
    double eps = 0.001;
    int answer = 1;
    if (common_dimension != state.common_dimension ||
        common_length!= state.common_length        ||
        dimension != state.dimension               ||
        length != state.length
       )
        answer = 0;
    if (answer != 0) {
        for (int i = 0; i < length; i++)  {
            if (abs(vec[i] - state.vec[i]) > eps) {
                answer = 0;
                break;
            }
        }
    }
    shmem_int_allsum(&answer);
    if (answer < size)
        return false;
    return true;
}

bool State::operator!=(State state)
{
    if (*this == state)
        return false;
    else
        return true;
}


int State::getRank()
{
    return rank;
}

int State::getSize()
{
    return size;
}

int State::getDimension()
{
    return common_dimension;
}

int State::getMyDimension()
{
    return dimension;
}

long long State::getLong()
{
    return common_length;
}

long long State::getMyLong()
{
    return length;
}

complexd* State::getMyVector()
{
    return vec;
}


istream &operator>>(istream &stream, State &state)
{
    stream.seekg(0, ios::beg);
    pointer = 0;

    for (int i = 0; i < state.size; i++) {
        shmem_barrier_all();
        if (state.rank == i) {
cout << "I am process  =  " << state.rank << "  and i begin" << endl;
cout.flush();
            stream.seekg(pointer, ios::beg);
            for (long long j = 0; j < state.length; j++)
                stream >> state.vec[j];
            pointer = stream.tellg();
cout << "I am process  =  " << state.rank << "  and i work" << endl;
cout.flush();
            if (state.rank != state.size - 1)
                shmem_long_put(&pointer, &pointer, sizeof(long), state.rank + 1);
cout << "I am process  =  " << state.rank << "  and i finish" << endl;
cout.flush();
        }
        shmem_barrier_all();
    }
    return stream;
}

ostream& operator<<(ostream &stream, State &state)
{
    for (int i = 0; i < state.size; i++) {
        if (state.rank == i) {
cout << "I am process  =  " << state.rank << "  and i begin - write" << endl;
cout.flush();
            stream.seekp(0, ios::end);
            for (long long j = 0; j < state.length; j++)
                stream << state.vec[j] << " ";
            stream.flush();
cout << "I am process  =  " << state.rank << "  and i work - write" << endl;
cout.flush();
            if (state.rank == state.size - 1)
                stream << endl;
            stream.flush();
cout << "I am process  =  " << state.rank << "  and i finish - write" << endl;
cout.flush();
        }
        shmem_barrier_all();
    }
    return stream;
}


void State::output(char *filename)
{
    /*
    MPI_File out_fd;
    MPI_File_open(MPI_COMM_WORLD, filename, MPI_MODE_WRONLY | MPI_MODE_CREATE,
                                    MPI_INFO_NULL, &out_fd);
    //MPI_File_set_view(out_fd, 0, MPI_DOUBLE_COMPLEX, MPI_DOUBLE_COMPLEX, (char *)"external32", MPI_INFO_NULL);
    MPI_File_write_at_all(out_fd, rank * length * sizeof(MPI_DOUBLE_COMPLEX),
                                            vec, length, MPI_DOUBLE_COMPLEX, 0);
    MPI_File_close(&out_fd);
    */
}

void State::input(char *filename)
{
    /*
    MPI_File in_fd;
    MPI_File_open(MPI_COMM_WORLD, filename, MPI_MODE_RDONLY,
                                    MPI_INFO_NULL, &in_fd);
    //MPI_File_set_view(in_fd, 0, MPI_DOUBLE_COMPLEX, MPI_DOUBLE_COMPLEX, (char *)"external32", MPI_INFO_NULL);
    MPI_File_read_at_all(in_fd, rank * length * sizeof(MPI_DOUBLE_COMPLEX),
                                          vec, length, MPI_DOUBLE_COMPLEX, 0);
    MPI_File_close(&in_fd);
    */
}


void State::info()
{
    cout << "My rank = " << rank  << "(all rocess " << size << ")"<< endl;

    cout << "Dimension = "        << dimension        << endl;
    cout << "Common dimension = " << common_dimension << endl;
    cout << "Length = "           << length           << endl;
    cout << "Common length = "       << common_length    << endl;
    cout << "Begin = "            << begin            << endl;
    cout << "End = "              << end              << endl;

    output();
}

void State::output()
{
    for (int i = 0; i < size; i++) {
        if (rank == i) {
            for (int j = 0; j < length; j++)
                cout << vec[j] << " ";
            cout << endl;
        }
        shmem_barrier_all();
    }
}
